﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Neuron.Cell;
using Neuron.Cell.Control;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Xml;

namespace Neuron
{

    public partial class ctrlPerceptronDrawingBoard : UserControl
    {

        private ctrlPerceptron _connectStart = null;
        private ctrlPerceptron _connectEnd = null;

        private PerceptronBackbone _backbone = null;

        private double _perceptronHardlimit = 1.0;

        public delegate void delegateUpdateNumberOfPerceptrons(int numberOfPerceptrons);
        public delegate void delegateUpdateFilename(string filename);
        public delegate void delegateFileLoaded();

        public event delegateUpdateNumberOfPerceptrons UpdateNumberOfPerceptrons;
        public event delegateUpdateFilename FilenameUpdated;
        public event delegateFileLoaded FileLoaded;

        private string _filename = "none";

        public ctrlPerceptronDrawingBoard()
        {
            InitializeComponent();

            horzScrollBar.Maximum = panelScreen.Width - panelViewPort.Width;
            vertScrollBar.Maximum = panelScreen.Height - panelViewPort.Height;

            panelScreen.AllowDrop = true;
            panelMiddle.AllowDrop = true;
            panelViewPort.AllowDrop = true;

            toolTipDrawingBoard.SetToolTip(buttonLoad, "Opens a perceptron network with learned status.");
            toolTipDrawingBoard.SetToolTip(buttonSave, "Saves a perceptron network with learned status.");
            toolTipDrawingBoard.SetToolTip(btnImport, "Imports a perceptron network in initialized status.");
            toolTipDrawingBoard.SetToolTip(btnExport, "Saves a perceptron network in initialized status.");
            toolTipDrawingBoard.SetToolTip(buttonClear, "Clears the drawing board.");

        }

        /*
        private void ClonePerceptron(Perceptron p)
        {
            string actFunc = "";
            switch (p.ActivationFunction)
            {
                case ActivationFunctionEnum.actor:
                    actFunc = "ACT";
                    break;
                case ActivationFunctionEnum.receptor:
                    actFunc = "REC";
                    break;
                case ActivationFunctionEnum.hlim:
                    actFunc = "HLIM";
                    break;
                case ActivationFunctionEnum.pwl:
                    actFunc = "PWL";
                    break;
                case ActivationFunctionEnum.sig:
                    actFunc = "SIG";
                    break;
                default:
                    actFunc = "HLIM";
                    break;
            }

            string newName = "P." + actFunc + "." + Convert.ToString(panelScreen.Controls.Count);
            Perceptron clonedPerceptron = this.createPerceptron(this._backbone, newName, actFunc, p.HardLimit, p.OnNeuronConnected);
            Perceptron pTMP = null;
            for (int i = 0; i < p.InputVectorNumberOfInputNeurons; i++)
            {
                pTMP = p.InputVectorGetPerceptronAtIndex(i);
                double weightOfInput = p.InputVectorGetWeightingOfPerceptronAtIndex(i);
                clonedPerceptron.addInputChannel(pTMP, weightOfInput);
            }

            foreach (Control ctrl in this.panelScreen.Controls)
            {
                if (ctrl is ctrlPerceptron)
                {
                    pTMP = ((ctrlPerceptron)ctrl).Perceptron;
                    for (int i = 0; i < pTMP.InputVectorNumberOfInputNeurons; i++)
                    {
                        Perceptron pInput = pTMP.InputVectorGetPerceptronAtIndex(i);
                        if (pInput.Name == p.Name)
                        {
                            double myWeight = pTMP.InputVectorGetWeightingOfPerceptronAtIndex(i);
                            pTMP.addInputChannel(clonedPerceptron, myWeight);
                        }
                    }
                }
            }

            createPerceptronControlAt(100, 100, clonedPerceptron);
        }
        */

        public string Filename
        {
            get { return this._filename; }
        }

        public void setBackbone(PerceptronBackbone backbone)
        {
            this._backbone = backbone;
            //this._backbone.ClonePerceptron += this.ClonePerceptron;
        }

        private void vertScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            vertScrollBar.Maximum = panelScreen.Height - panelViewPort.Height;
            panelScreen.Top = -vertScrollBar.Value;
        }

        private void horzScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            horzScrollBar.Maximum = panelScreen.Width - panelViewPort.Width;
            panelScreen.Left = -horzScrollBar.Value;
        }

        public Panel MainScreen
        {
            get { return this.panelScreen; }
        }

        private void panelScreen_DragDrop(object sender, DragEventArgs e)
        {
            int mouseX = e.X;
            int mouseY = e.Y;

            string strData = (String)e.Data.GetData(DataFormats.StringFormat);
            string newName = "P." + strData + "." + Convert.ToString(panelScreen.Controls.Count);

            Perceptron p = null;

            // CALL CreatePerceptron
            // TODO
            string[] strActFunc = strData.Split('_');
            bool onNeuron = false;
            if (strActFunc.Count() > 1)
            {
                onNeuron = true;
            }
            strActFunc[0] = strActFunc[0].Trim('_').ToLower();
            strData = strActFunc[0];

            Perceptron newP = this.createPerceptron(this._backbone, newName, strData, this._perceptronHardlimit, onNeuron);

            Point mousePoint = new Point(mouseX, mouseY);
            Point relMousePoint = panelScreen.PointToClient(mousePoint);
            //ctrlP.Left = relMousePoint.X;
            //ctrlP.Top = relMousePoint.Y;

            ctrlPerceptron newCTRLPerceptron = this.createPerceptronControlAt(Convert.ToInt32(relMousePoint.X), Convert.ToInt32(relMousePoint.Y), newP);

            //panelScreen.Controls.Add(newCTRLPerceptron);
            //newCTRLPerceptron.Parent = panelScreen;
        }

        private void PerceptronStartConnection(ctrlPerceptron pStart)
        {
            this._connectStart = pStart;
        }

        private void PerceptronEndConnection(ctrlPerceptron pEnd)
        {
            this._connectEnd = pEnd;

            if ((this._connectStart != null) && (this._connectEnd != null))
            {
                Perceptron perceptronStart = this._connectStart.Perceptron;
                Perceptron perceptronEnd = this._connectEnd.Perceptron;

                if (perceptronEnd.addInputChannel(perceptronStart) == true)
                    this._connectEnd.DrawClientConnectionsAutomatically();

                this._connectStart = null;
                this._connectEnd = null;
            }
        }

        public int NumberOfPerceptrons
        {
            get { return this.panelScreen.Controls.Count; }
        }

        private void PerceptronMovementStopped()
        {
            this.RedrawConnections();
        }

        public void RedrawConnections()
        {
            panelScreen.Refresh();

            foreach (System.Windows.Forms.Control ctrlChild in panelScreen.Controls)
            {
                if (ctrlChild is ctrlPerceptron)
                {
                    ctrlPerceptron tmpCtrlPerceptron = (ctrlPerceptron)ctrlChild;
                    Perceptron tmpPerceptron = tmpCtrlPerceptron.Perceptron;
                    tmpCtrlPerceptron.DrawClientConnectionsAutomatically();
                }
            }
        }

        private void panelScreen_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.StringFormat))
            {
                e.Effect = DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void panelScreen_MouseMove(object sender, MouseEventArgs e)
        {
            if ((this._connectStart != null) && (this._connectEnd == null))
            {
                panelScreen.Cursor = Cursors.Cross;
            }
            if ((this._connectStart == null) && (this._connectEnd == null))
            {
                panelScreen.Cursor = Cursors.Arrow;
            }
        }

        private void panelScreen_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                this._connectStart = null;
                this._connectEnd = null;
            }
        }

        private void panelScreen_MouseUp(object sender, MouseEventArgs e)
        {
            /*
            if (e.Button == MouseButtons.Left)
            {
                this.RedrawConnections();
            }
            */
        }

        private void buttonClear_Click(object sender, EventArgs e)
        {
            this.DeleteAllPerceptrons();
        }

        // Objectfabriken, um dioe Controls und die Perceptrons selsbt zu erstellen
        public ctrlPerceptron createPerceptronControlAt(int x, int y, Perceptron p)
        {
            ctrlPerceptron ctrlP = new ctrlPerceptron();
            ctrlP.Perceptron = p;
            ctrlP.Left = x;
            ctrlP.Top = y;
            ctrlP.ClickStartConnection += this.PerceptronStartConnection;
            ctrlP.ClickEndConnection += this.PerceptronEndConnection;
            ctrlP.MovementStopped += this.PerceptronMovementStopped;
            ctrlP.DeleteMe += this.DeletePerceptron;

            panelScreen.Controls.Add(ctrlP);
            ctrlP.Parent = panelScreen;

            if (this.UpdateNumberOfPerceptrons != null)
            {
                this.UpdateNumberOfPerceptrons(this.panelScreen.Controls.Count);
            }

            return ctrlP;
        }

        private Perceptron createPerceptron(PerceptronBackbone backbone, String name, String activationFunction, double hardLimit, bool createOnNeuron)
        {
            Perceptron p = null;

            string actFunc = activationFunction.ToUpper();
            if (createOnNeuron == true)
            {
                actFunc += "_ON";
            }

            switch (actFunc)
            {
                case "HLIM":
                    p = new Perceptron(backbone, name, hardLimit, ActivationFunctionEnum.hlim, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "HLIM_ON":
                    p = new Perceptron(backbone, name, hardLimit, ActivationFunctionEnum.hlim, OnNeuronEnum.withOnNeuron);
                    break;
                case "PWL":
                    p = new Perceptron(backbone, name, hardLimit, ActivationFunctionEnum.pwl, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "PWL_ON":
                    p = new Perceptron(backbone, name, hardLimit, ActivationFunctionEnum.pwl, OnNeuronEnum.withOnNeuron);
                    break;
                case "SIG":
                    p = new Perceptron(backbone, name, hardLimit, ActivationFunctionEnum.sig, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "SIG_ON":
                    p = new Perceptron(backbone, name, hardLimit, ActivationFunctionEnum.sig, OnNeuronEnum.withOnNeuron);
                    break;
                case "REC":
                    p = new Perceptron(backbone, name, 1.0, ActivationFunctionEnum.receptor, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "RECEPTOR":
                    p = new Perceptron(backbone, name, 1.0, ActivationFunctionEnum.receptor, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "ACT":
                    p = new Perceptron(backbone, name, 0.5, ActivationFunctionEnum.actor, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "ACTOR":
                    p = new Perceptron(backbone, name, 0.5, ActivationFunctionEnum.actor, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "BUF":
                    p = new Perceptron(backbone, name, 0.5, ActivationFunctionEnum.buffer, OnNeuronEnum.withoutOnNeuron);
                    break;
                case "TMR":
                    p = new Perceptron(backbone, name, 0.5, ActivationFunctionEnum.trigger, OnNeuronEnum.withoutOnNeuron);
                    break;
                default:
                    break;
            }

            return p;
        }

        // Filehandlingroutinen
        private void SavePerceptronToDisk(StreamWriter stream, Perceptron p, bool inclWeighting)
        {
            String pName = p.Name;
            double pHardLimit = p.HardLimit;
            string pActivationFunction = "";
            switch (p.ActivationFunction)
            {
                case ActivationFunctionEnum.actor:
                    {
                        pActivationFunction = "act";
                        break;
                    }
                case ActivationFunctionEnum.receptor:
                    {
                        pActivationFunction = "rec";
                        break;
                    }
                case ActivationFunctionEnum.hlim:
                    {
                        pActivationFunction = "hlim";
                        break;
                    }
                case ActivationFunctionEnum.pwl:
                    {
                        pActivationFunction = "pwl";
                        break;
                    }
                case ActivationFunctionEnum.sig:
                    {
                        pActivationFunction = "sig";
                        break;
                    }
                default:
                    pActivationFunction = "hlim";
                    break;
            }

            bool pOnNeuronConnected = p.OnNeuronConnected;
            int pLeft = p.Control.Left;
            int pTop = p.Control.Top;

            string strLine = (pName + "," + pActivationFunction + "," + Convert.ToString(pHardLimit).Replace(',', '.') + "," + pOnNeuronConnected.ToString() + "," + Convert.ToString(pLeft) + "," + Convert.ToString(pTop) + ",");

            for (int i = 0; i < p.InputVectorNumberOfInputNeurons; i++)
            {
                Perceptron pInput = p.InputVectorGetPerceptronAtIndex(i);
                strLine += pInput.Name + ",";
                if (inclWeighting)
                {
                    double weighting = p.InputVectorGetWeightingOfPerceptronAtIndex(i);
                    string strWeight = Convert.ToString(Math.Round(weighting, 5)).Replace(',', '.');
                    strLine += strWeight + ",";
                }
            }
            strLine = strLine.TrimEnd(',');
            stream.WriteLine(strLine);
        }

        private void ImportXMLFile(string fileName, bool inclWeight)
        {
            XmlDocument networkDoc = new XmlDocument();
            networkDoc.Load(openFileDialog.FileName);

            foreach (XmlElement XmlPerceptronControl in networkDoc.DocumentElement)
            {
                string ctrlName = XmlPerceptronControl.Attributes["Name"].Value;
                int ctrlLeft = Convert.ToInt32(XmlPerceptronControl.Attributes["Left"].Value);
                int ctrlTop = Convert.ToInt32(XmlPerceptronControl.Attributes["Top"].Value);

                XmlElement XmlPerceptron = (XmlElement)XmlPerceptronControl.ChildNodes[0];
                string pName = XmlPerceptron.Attributes["Name"].Value;
                bool pBiasNeuron = Convert.ToBoolean(XmlPerceptron.Attributes["BiasNeuron"].Value);
                double pHardLimit = Double.Parse(XmlPerceptron.Attributes["HardLimit"].Value.Replace(',', '.'), System.Globalization.CultureInfo.InvariantCulture);
                string pActivationFunction = XmlPerceptron.Attributes["ActivationFunction"].Value;

                Perceptron p = this.createPerceptron(this._backbone, pName, pActivationFunction, pHardLimit, pBiasNeuron);
                ctrlPerceptron ctrlP = this.createPerceptronControlAt(ctrlLeft, ctrlTop, p);

                panelScreen.Controls.Add(ctrlP);
                ctrlP.Parent = panelScreen;
                ctrlP.DeleteMe += this.DeletePerceptron;
            }

            foreach (XmlElement XmlPerceptronControl in networkDoc.DocumentElement)
            {
                string ctrlName = XmlPerceptronControl.Attributes["Name"].Value;
                Perceptron tmpPerceptron = this._backbone.getPerceptronInstanceByName(ctrlName);

                XmlNodeList nodeList = networkDoc.SelectNodes(string.Format("//Network/Control[@Name='{0}']/Perceptron/*", ctrlName));
                foreach (XmlNode xmlNode in nodeList)
                {
                    XmlElement element = (XmlElement)xmlNode;
                    string senderPerceptronName = element.Name;
                    // ON Neuron wird nicht anghängt, da es sich bereits am Perceptron befindet (Constructor)
                    if (!senderPerceptronName.StartsWith("ON_"))
                    {
                        Perceptron senderPerceptron = this._backbone.getPerceptronInstanceByName(senderPerceptronName);
                        // Wird ein Import durchgeführt, werden die Gewichtungen eingelesen und dem Sender-Perceptron zugeordnet
                        if (inclWeight)
                        {
                            string strWeight = element.Attributes["weight"].Value.Replace(',', '.');
                            double weight = Double.Parse(strWeight, System.Globalization.CultureInfo.InvariantCulture);
                            tmpPerceptron.addInputChannel(senderPerceptron, weight);
                        }
                        else
                        {
                            // Ansonsten wird das Perceptron mit der Default-Gwichtung in den 1.0 in den InputVector eingefügt
                            tmpPerceptron.addInputChannel(senderPerceptron);
                        }
                    } else
                    {
                        // Die Wertigkeit des ON-Neuron wird auch nur dann gesetzt, wenn ein Import - also
                        // ein Laden mit übernahm eder Wertigkeiten aus derDatei durchgeführt wird
                        if (inclWeight)
                        {
                            string strWeight = element.Attributes["weight"].Value.Replace(',', '.');
                            double weight = Double.Parse(strWeight, System.Globalization.CultureInfo.InvariantCulture);
                            Perceptron BiasNeuron = this._backbone.getPerceptronInstanceByName(senderPerceptronName);
                            tmpPerceptron.SetWeightingOf(0, weight);
                        }
                    }
                }
                
            }
        }

        public XmlDocument getXMLStructure(bool inclWeighting)
        {
            XmlDocument XmlDocumentNetwork = new XmlDocument();
            XmlDeclaration xmlDeclaration = XmlDocumentNetwork.CreateXmlDeclaration("1.0", "UTF-8", null);
            XmlDocumentNetwork.InsertBefore(xmlDeclaration, XmlDocumentNetwork.DocumentElement);

            XmlElement XmlElementNetwork = XmlDocumentNetwork.CreateElement("Network");
            XmlDocumentNetwork.AppendChild(XmlElementNetwork);

            foreach (System.Windows.Forms.Control ctrlChild in panelScreen.Controls)
            {
                if (ctrlChild is ctrlPerceptron)
                {
                    ((ctrlPerceptron)ctrlChild).getXMLElement(XmlDocumentNetwork, XmlElementNetwork, inclWeighting);
                }
            }

            return XmlDocumentNetwork;
        }
        
        void saveNetwork(bool inclWeighting)
        {
            try
            {
                //saveFileDialog.Filter = "Neural Network Designer - Network-Export-File|*.net|Neural Network Designer - Network-Export-XMLFile|*.netxml";
                saveFileDialog.Filter = "Neural Network Designer - Network-Export-XMLFile|*.xml";
                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    this._filename = Path.GetFileNameWithoutExtension(saveFileDialog.FileName);

                    XmlDocument XmlDocumentNetwork = this.getXMLStructure(inclWeighting);
                    XmlDocumentNetwork.Save(saveFileDialog.FileName);
                }

                if (this.FilenameUpdated != null)
                {
                    this.FilenameUpdated(this._filename);
                }
            } catch(Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        private void loadNetwork(bool loadWeighting)
        {
            try
            {
                this._filename = "none";
                //openFileDialog.Filter = "Neural Network Designer - Networkfile|*.net|Neural Network Designer - XML-Networkfile|*.netxml";
                openFileDialog.Filter = "Neural Network Designer - XML-Networkfile|*.xml";

                if (openFileDialog.ShowDialog() == DialogResult.OK)
                {
                    this.DeleteAllPerceptrons();
                    panelScreen.Refresh();

                    this._filename = Path.GetFileNameWithoutExtension(openFileDialog.FileName);

                    this.ImportXMLFile(openFileDialog.FileName, loadWeighting);
                }

                if (this.FilenameUpdated != null)
                {
                    this.FilenameUpdated(this._filename);
                }

                this.RedrawConnections();

                if (this.FileLoaded != null)
                {
                    this.FileLoaded();
                }
            } catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        private void buttonLoad_Click(object sender, EventArgs e)
        {
            this.loadNetwork(false);
        }

        private void btnImport_Click(object sender, EventArgs e)
        {
            this.loadNetwork(true);
        }

        // Buttoneventhandler zu den Filehandlingroutinen
        private void buttonSave_Click(object sender, EventArgs e)
        {
            this.saveNetwork(false);
        }

        private void btnExport_Click(object sender, EventArgs e)
        {
            this.saveNetwork(true);
        }

        // Interfaceroutinen zur Ansteuerung aus dem Hauptformular heraus
        public void exportFile(Object sender, EventArgs e)
        {
            this.btnExport_Click(sender, e);
        }

        public void importFile(Object sender, EventArgs e)
        {
            this.btnImport_Click(sender, e);
        }

        public void openFile(Object sender, EventArgs e)
        {
            this.buttonLoad_Click(sender, e);
        }

        public void saveFile(Object sender, EventArgs e)
        {
            this.buttonSave_Click(sender, e);
        }

        public void clearDrawingBoard(Object sender, EventArgs e)
        {
            this.buttonClear_Click(sender, e);
        }

        private void DeletePerceptron(ctrlPerceptron p)
        {
            Perceptron tmpPerceptron = p.Perceptron;
            foreach (Control ctrl in panelScreen.Controls)
            {
                Perceptron searchPerceptron = ((ctrlPerceptron)ctrl).Perceptron;
                //if (tmpPerceptron.Name == searchPerceptron.Name)
                //{
                    searchPerceptron.InputVectorRemovePerceptronFrom(tmpPerceptron);
                //}
            }
            this._backbone.DeletePerceptron(p.Perceptron.Name);

            p.Perceptron.InputVectorClear();

            p.Perceptron = null;
            panelScreen.Controls.Remove(p);
            p.Dispose();
            p = null;

            if (this.UpdateNumberOfPerceptrons != null)
            {
                this.UpdateNumberOfPerceptrons(this.panelScreen.Controls.Count);
            }
        }

        private void DeleteAllPerceptrons()
        {
            foreach (Control ctrl in MainScreen.Controls)
            {
                if (ctrl is ctrlPerceptron)
                {
                    Perceptron p = ((ctrlPerceptron)ctrl).Perceptron;
                    this._backbone.DeletePerceptron(p.Name);
                    ((ctrlPerceptron)ctrl).Perceptron = null;
                }
            }

            while (MainScreen.Controls.Count > 0)
            {
                MainScreen.Controls[0].Dispose();
            }

            panelScreen.Refresh();

            if (this.UpdateNumberOfPerceptrons != null)
            {
                this.UpdateNumberOfPerceptrons(this.panelScreen.Controls.Count);
            }

            this._filename = "none";
            if (this.FilenameUpdated != null)
            {
                this.FilenameUpdated(this._filename);
            }
        }

        private void panelScreen_Resize(object sender, EventArgs e)
        {
            this.RedrawConnections();
        }

        private void vertScrollBar_MouseLeave(object sender, EventArgs e)
        {
            this.RedrawConnections();
        }

        private void horzScrollBar_MouseLeave(object sender, EventArgs e)
        {
            this.RedrawConnections();
        }

        private void panelViewPort_Resize(object sender, EventArgs e)
        {
            this.RedrawConnections();
        }
    }
}
